# BeanPersister.java

    package org.codefilarete.stalactite.engine.runtime;

    import java.util.Collections;
    import java.util.Set;
    import java.util.function.Consumer;

    import org.codefilarete.stalactite.engine.EntityCriteria;
    import org.codefilarete.stalactite.engine.PersistExecutor;
    import org.codefilarete.stalactite.engine.PersistenceContext;
    import org.codefilarete.stalactite.mapping.DefaultEntityMapping;
    import org.codefilarete.stalactite.mapping.EntityMapping;
    import org.codefilarete.stalactite.mapping.SimpleIdMapping;
    import org.codefilarete.stalactite.mapping.id.manager.AlreadyAssignedIdentifierManager;
    import org.codefilarete.stalactite.query.model.Select;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.ConnectionProvider;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.statement.DMLGenerator;
    import org.codefilarete.stalactite.sql.statement.WriteOperation;
    import org.codefilarete.stalactite.sql.statement.WriteOperationFactory;
    import org.codefilarete.tool.Duo;
    import org.codefilarete.tool.exception.NotImplementedException;

    /**
     * Main class for CRUD operations of an entity class. Doesn't manage relation between entities.
     * Delegates SQL operations to {@link InsertExecutor}, {@link UpdateExecutor}, {@link DeleteExecutor} and
     * {@link SelectExecutor}.
     * Will wrap every CRUD operation with dedicated listener before / after method invokation.
     *
     * @param <C> entity type
     * @param <I> identifier type
     * @param <T> target table type
     * @author Guillaume Mary
     */
    public class BeanPersister<C, I, T extends Table<T>>
    		extends PersisterListenerWrapper<C, I>
    		implements ConfiguredPersister<C, I> {

    	/**
    	 * Property mapping on which SQL executors will refer to build their SQL operation
    	 */
    	private final EntityMapping<C, I, T> mappingStrategy;

    	/**
    	 * Connection on which SQL operations will be run
    	 */
    	private final ConnectionConfiguration connectionConfiguration;

    	/**
    	 * SQL generator for CRUD operations
    	 */
    	private final DMLGenerator dmlGenerator;

    	/**
    	 * Factory of {@link WriteOperation}
    	 */
    	private final WriteOperationFactory writeOperationFactory;

    	private final PersistExecutor<C> persistExecutor;
    	private final InsertExecutor<C, I, T> insertExecutor;
    	private final UpdateExecutor<C, I, T> updateExecutor;
    	private final DeleteExecutor<C, I, T> deleteExecutor;
    	private final org.codefilarete.stalactite.engine.SelectExecutor<C, I> selectExecutor;

    	public BeanPersister(EntityMapping<C, I, T> mappingStrategy, PersistenceContext persistenceContext) {
    		this(mappingStrategy, persistenceContext.getDialect(), persistenceContext.getConnectionConfiguration());
    	}

    	public BeanPersister(EntityMapping<C, I, T> mappingStrategy, Dialect dialect, ConnectionConfiguration connectionConfiguration) {
    		this.mappingStrategy = mappingStrategy;
    		this.connectionConfiguration = connectionConfiguration;
    		this.dmlGenerator = dialect.getDmlGenerator();
    		this.writeOperationFactory = dialect.getWriteOperationFactory();
    		this.persistExecutor = newPersistExecutor();
    		this.insertExecutor = newInsertExecutor(this.mappingStrategy, this.connectionConfiguration, dmlGenerator,
    				writeOperationFactory, dialect.getInOperatorMaxSize());
    		this.updateExecutor = newUpdateExecutor(this.mappingStrategy, this.connectionConfiguration, dmlGenerator,
    				writeOperationFactory, dialect.getInOperatorMaxSize());
    		this.deleteExecutor = newDeleteExecutor(this.mappingStrategy, this.connectionConfiguration, dmlGenerator,
    				writeOperationFactory, dialect.getInOperatorMaxSize());
    		this.selectExecutor = newSelectExecutor(this.mappingStrategy, this.connectionConfiguration, dialect);
    	}

    	protected PersistExecutor<C> newPersistExecutor() {
    		return new DefaultPersistExecutor<>(this);
    	}

    	protected InsertExecutor<C, I, T> newInsertExecutor(EntityMapping<C, I, T> mappingStrategy,
    														ConnectionConfiguration connectionConfiguration,
    														DMLGenerator dmlGenerator,
    														WriteOperationFactory writeOperationFactory,
    														int inOperatorMaxSize) {
    		return new InsertExecutor<>(mappingStrategy, connectionConfiguration, dmlGenerator,
    				writeOperationFactory, inOperatorMaxSize);
    	}

    	protected UpdateExecutor<C, I, T> newUpdateExecutor(EntityMapping<C, I, T> mappingStrategy,
    														ConnectionConfiguration connectionConfiguration,
    														DMLGenerator dmlGenerator,
    														WriteOperationFactory writeOperationFactory,
    														int inOperatorMaxSize) {
    		return new UpdateExecutor<>(mappingStrategy, connectionConfiguration, dmlGenerator,
    				writeOperationFactory, inOperatorMaxSize);
    	}

    	protected DeleteExecutor<C, I, T> newDeleteExecutor(EntityMapping<C, I, T> mappingStrategy,
    														ConnectionConfiguration connectionConfiguration,
    														DMLGenerator dmlGenerator,
    														WriteOperationFactory writeOperationFactory,
    														int inOperatorMaxSize) {
    		return new DeleteExecutor<>(mappingStrategy, connectionConfiguration, dmlGenerator,
    				writeOperationFactory, inOperatorMaxSize);
    	}

    	protected org.codefilarete.stalactite.engine.SelectExecutor<C, I> newSelectExecutor(EntityMapping<C, I, T> mappingStrategy,
    																						ConnectionConfiguration connectionConfiguration,
    																						Dialect dialect) {
    		return new org.codefilarete.stalactite.engine.runtime.SelectExecutor<>(
    				mappingStrategy,
    				connectionConfiguration,
    				dialect.getDmlGenerator(),
    				dialect.getReadOperationFactory(),
    				dialect.getInOperatorMaxSize());
    	}

    	public ConnectionProvider getConnectionProvider() {
    		return connectionConfiguration.getConnectionProvider();
    	}

    	public DMLGenerator getDmlGenerator() {
    		return dmlGenerator;
    	}

    	@Override
    	public Class<C> getClassToPersist() {
    		return getMapping().getClassToPersist();
    	}

    	@Override
    	public EntityMapping<C, I, T> getMapping() {
    		return mappingStrategy;
    	}

    	public T getMainTable() {
    		return getMapping().getTargetTable();
    	}

    	/**
    	 * Gives all tables implied in the persistence of the entity such as joined tables or duplicating tables (depending
    	 * on the implementation of this {@link BeanPersister})
    	 * Useful to generate schema creation script.
    	 *
    	 * @return a {@link Set} of implied tables, not expected to be writable
    	 */
    	public Set<Table<?>> giveImpliedTables() {
    		return Collections.singleton(getMainTable());
    	}

    	public InsertExecutor<C, I, T> getInsertExecutor() {
    		return insertExecutor;
    	}

    	public UpdateExecutor<C, I, T> getUpdateExecutor() {
    		return updateExecutor;
    	}

    	public DeleteExecutor<C, I, T> getDeleteExecutor() {
    		return deleteExecutor;
    	}

    	public org.codefilarete.stalactite.engine.SelectExecutor<C, I> getSelectExecutor() {
    		return selectExecutor;
    	}

    	@Override
    	public ExecutableEntityQuery<C, ?> selectWhere() {
    		throw new NotImplementedException("Not yet implemented");
    	}

    	@Override
    	public ExecutableProjectionQuery<C, ?> selectProjectionWhere(Consumer<SelectAdapter<C>> selectAdapter) {
    		throw new NotImplementedException("Not yet implemented");
    	}

    	@Override
    	public Set<C> selectAll() {
    		throw new NotImplementedException("Not yet implemented");
    	}

    	/**
    	 * Saves given entities : will apply insert or update according to entities persistent state.
    	 * Triggers cascade on relations. Please note that in case of already-persisted entities (not new), entities will be reloaded from database
    	 * to compute differences and cascade only modifications.
    	 * If one already has a copy of the "unmodified" instance, you may prefer to directly use {@link #update(Object, Object, boolean)}
    	 *
    	 * @param entities some entities, can be a mix of none-persistent or already-persisted entities
    	 */
    	@Override
    	protected void doPersist(Iterable<? extends C> entities) {
    		persistExecutor.persist(entities);
    	}

    	@Override
    	protected void doInsert(Iterable<? extends C> entities) {
    		insertExecutor.insert(entities);
    	}

    	@Override
    	protected void doUpdateById(Iterable<? extends C> entities) {
    		updateExecutor.updateById(entities);
    	}

    	@Override
    	protected void doUpdate(Iterable<? extends Duo<C, C>> entities, boolean allColumnsStatement) {
    		updateExecutor.update(entities, allColumnsStatement);
    	}

    	@Override
    	protected void doDelete(Iterable<? extends C> entities) {
    		deleteExecutor.delete(entities);
    	}

    	@Override
    	protected void doDeleteById(Iterable<? extends C> entities) {
    		deleteExecutor.deleteById(entities);
    	}

    	@Override
    	protected Set<C> doSelect(Iterable<I> ids) {
    		return selectExecutor.select(ids);
    	}

    	/**
    	 * Indicates if a bean is persisted or not. Delegated to {@link DefaultEntityMapping}
    	 *
    	 * @param c a bean
    	 * @return true if a bean is already persisted
    	 * @see DefaultEntityMapping#isNew(Object)
    	 * @see SimpleIdMapping.IsNewDeterminer
    	 */
    	@Override
    	public boolean isNew(C c) {
    		return mappingStrategy.isNew(c);
    	}
    }